package com.pragmatic.cryptography;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * AES加解密demo: https://blog.csdn.net/qq_56158663/article/details/147112728
 *
 * @author wangdong
 * @since 2025/4/21 14:37
 */
public class SimpleAesCryptography {

  // 算法名称
  private static final String ALGORITHM = "AES";
  // AES/模式/填充方式
  private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";

  /**
   * AES加密
   *
   * @param plainText 待加密的明文
   * @param key AES秘钥
   * @param iv 初始化向量(由于随机向量的生成，使得相同的秘钥 + 相同的明文，生成的明文不一致)
   * @return 密文
   * @throws Exception 抛出异常
   */
  public static String encrypt(String plainText, SecretKey key, IvParameterSpec iv)
      throws Exception {
    // 1. 获取Cipher实例，指定算法、模式、填充方式
    Cipher cipher = Cipher.getInstance(TRANSFORMATION);
    // 2. 初始化Cipher为加密模式
    // 需要密钥和初始化向量(IV)，IV保证相同明文每次加密结果不同
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    // 3. 执行加密操作
    byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
    // 4. 将字节数组转换为Base64编码的字符串，方便传输
    return Base64.getEncoder().encodeToString(encryptedBytes);
  }

  /**
   * AES解密
   *
   * @param encryptedText 待解密的密文
   * @param key AES秘钥
   * @param iv 初始化向量(这里要和加密时一致,一般加密后会带过来)
   * @return 明文
   * @throws Exception 抛出异常
   */
  public static String decrypt(String encryptedText, SecretKey key, IvParameterSpec iv)
      throws Exception {
    // 1. 获取Cipher实例，指定算法、模式、填充方式
    Cipher cipher = Cipher.getInstance(TRANSFORMATION);
    // 2. 初始化Cipher为解密模式
    cipher.init(Cipher.DECRYPT_MODE, key, iv);
    // 3. 先将Base64编码的字符串解码为字节数组
    byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText);
    // 4. 执行解密操作
    byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
    // 5. 将解密后的字节数组转换为字符串
    return new String(decryptedBytes, StandardCharsets.UTF_8);
  }

  /**
   * 生成安全的初始化向量 IV
   *
   * @return 初始化的向量对象
   */
  public static IvParameterSpec generateIv() {
    // AES块大小为16字节 (128 bit)
    byte[] iv = new byte[16];
    // 使用密码学安全的随机数生成器
    new SecureRandom().nextBytes(iv);
    return new IvParameterSpec(iv);
  }

  // =================================================================================================
  // =================================================================================================
  // =================================================================================================

  // main方法自测
  public static void main(String[] args) throws Exception {
    // ---- 密钥管理 ----
    // 实际应用中，密钥需要安全生成和存储，不能硬编码! ️
    // 方法1: 随机生成密钥 (推荐)
    KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
    keyGen.init(128); // 使用128位密钥
    SecretKey secretKey = keyGen.generateKey();

    // 方法2: 从已知字节数组生成 (例如，从配置文件或安全存储中读取)
    // byte[] keyBytes = "ThisIsA128BitKey".getBytes("UTF-8"); // 必须是16, 24, 或 32字节长
    // SecretKey secretKey = new SecretKeySpec(keyBytes, ALGORITHM);

    // ---- 初始化向量 (IV) ----
    // IV 必须是随机生成的，并且每次加密都不同 (对于CBC, GCM等模式)
    // IV 通常与密文一起传输，它不需要保密，但必须唯一
    IvParameterSpec ivParameterSpec = generateIv(); // 每次加密前生成新的IV

    // ---- 测试 ----
    String originalText = "这是一段需要加密的秘密信息！☺";
    System.out.println("原始信息: " + originalText);
    // 加密时使用生成的 IV
    String encryptedText = encrypt(originalText, secretKey, ivParameterSpec);
    System.out.println("加密后(密文): " + encryptedText);
    // 解密时需要使用加密时相同的 IV
    // 在实际应用中，IV 通常会和密文一起传输或存储
    String decryptedText = decrypt(encryptedText, secretKey, ivParameterSpec);
    System.out.println("解密后(明文): " + decryptedText);
  }
}
